package com.app.logger.aspect;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.app.core.constant.Constants;
import com.app.core.exception.ExceptionTranslator;
import com.app.core.mvc.result.Result;
import com.app.core.security.Principal;
import com.app.core.security.ResourceOwnerContext;
import com.app.core.util.SpelUtil;
import com.app.kit.AopUtil;
import com.app.kit.IPUtils;
import com.app.kit.WebUtil;
import com.app.logger.model.Logger;
import com.app.logger.model.LoggerType;
import com.app.logger.service.LoggerService;
import com.google.common.base.Stopwatch;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.Date;
import java.util.Map;
import java.util.Objects;

/**
 * 系统日志，切面处理类
 * 正常情况下执行顺序：Around start -> Before -> Around end -> After -> AfterReturning
 * 有异常情况下执行顺序：Around start -> Before -> After -> AfterThrowing ，有异常时，Around end 逻辑不会执行
 *
 * @author qiangt
 * @since 2022-11-29
 */
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class LoggerAspect {

    private final LoggerService loggerService;
    private final ResourceOwnerContext resourceOwnerContext;
    private final ExceptionTranslator exceptionTranslator;
    private Stopwatch stopwatch;

    @Pointcut("@annotation(com.app.logger.model.Logger)")
    public void pointcut() {

    }

    @Before(value = "pointcut()")
    public void before() {
        this.stopwatch = Stopwatch.createStarted();
    }

    @AfterThrowing(value = "pointcut()", throwing = "e")
    public void afterThrowing(JoinPoint point, Exception e) {
        // 保存日志
        this.prepareLog(point, this.stopwatch.stop().elapsed(), this.exceptionTranslator.translateException(e), e);
    }

    private void prepareLog(JoinPoint joinPoint, Duration duration, Object result, Throwable throwable) {
        try {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            Logger logger = method.getAnnotation(Logger.class);
            if (Objects.isNull(logger)) {
                return;
            }
            String requestResult = null;
            if (result instanceof Result) {
                if (Objects.isNull(throwable)) {
                    throwable = ((Result<?>) result).getThrowable();
                }
                requestResult = JSON.toJSONString(result);
            }
            Principal principal = null;
            if (ObjectUtil.equal(logger.type(), LoggerType.LOGIN)) {
                principal = resourceOwnerContext.getPrincipal(Convert.toStr(((Result) result).getData()));
            } else {
                principal = resourceOwnerContext.getPrincipal();
            }
            String errorInfo = Objects.nonNull(throwable) ? ExceptionUtils.getStackTrace(throwable) : null;
            HttpServletRequest request = WebUtil.getRequest();
            com.app.logger.entity.Logger log = new com.app.logger.entity.Logger();
            log.setClientIp(IPUtils.getIpAddr(request));
            log.setRequestParam(JSON.toJSONString(AopUtil.getParams(joinPoint)));
            log.setRequestType(request.getMethod());
            log.setRequestUri(request.getRequestURI());
            log.setRequestMethod(String.format("%s.%s()", joinPoint.getTarget().getClass().getName(), signature.getName()));
            log.setLogType(logger.type());
            log.setUserId(principal.getPrimaryKey().toString());
            log.setUsername(principal.getName());
            log.setCostTime(duration.toMillis());
            log.setXid(MDC.get(Constants.TRACE_KEY));
            log.setErrorInfo(errorInfo);
            log.setRequestResult(requestResult);
            log.setCreateBy(Convert.toLong(principal.getPrimaryKey()));
            log.setUpdateBy(Convert.toLong(principal.getPrimaryKey()));
            log.setCreateTime(new Date());
            log.setUpdateTime(new Date());
            Map<String, Object> params = AopUtil.getParams(joinPoint);
            log.setDescription(Convert.toStr(SpelUtil.calculate(logger.value(), params, principal)));
            LoggerAspect.log.info("记录操作日志:{}", log.getXid());
            this.loggerService.save(log);
        } catch (Exception e) {
            LoggerAspect.log.info("记录操作日志失败：{}", e.getMessage(), e);
        }
    }

    @AfterReturning(value = "pointcut()", returning = "result")
    public void afterReturn(JoinPoint point, Object result) {
        if (result instanceof SseEmitter) {
            log.info("SseEmitter");
            return;
        }
        this.prepareLog(point, this.stopwatch.stop().elapsed(), result, null);
    }


}
